<?php
/*
 * @Author: 杨红兵 
 * @Date: 2022-07-13 14:11:42 
 * @Last Modified by: 杨红兵
 * @Last Modified time: 2022-09-13 16:49:00
 */

namespace app\libraries\utils;

use app\constdir\SysErrorCode;
use app\libraries\exception\ApiException;
use Closure;
use Swoole\Coroutine;
use Swoole\Coroutine\Channel;
use think\helper\Str;
use think\Http;
use think\Request;
use think\swoole\App;
use think\swoole\Sandbox;
use Throwable;

class Util
{
    /**
     * 是否在预发环境
     *
     * @Author YangHB
     * @DateTime 2022-07-12 13:35:48
     *
     * @return boolean
     */
    public static function isPre(): bool
    {
        return strtolower(env("APP_ENV")) == 'pre';
    }

    /**
     * 是否在线上环境
     *
     * @Author YangHB
     * @DateTime 2022-07-12 13:35:58
     *
     * @return boolean
     */
    public static function isProd(): bool
    {
        return strtolower(env("APP_ENV")) == 'prod';
    }

    /**
     * 是否测试环境，只要不是线上或灰度，就表示为测试环境
     *
     * @Author YangHB
     * @DateTime 2022-07-12 11:40:19
     *
     * @return boolean
     */
    public static function isTest(): bool
    {
        return !(static::isProd() || static::isPre());
    }

    /**
     * 返回环境字符串
     *
     * @Author YangHB
     * @DateTime 2022-07-12 13:36:25
     *
     * @return string
     */
    public static function getEnvStr(): string
    {
        if (self::isProd()) {
            return 'prod';
        }
        if (self::isPre()) {
            return 'pre';
        }
        return 'test';
    }

    /**
     * 抛出异常
     *
     * @param  array  $error
     * @param  array  $args
     * @param  array  $logData
     * @param  string  $level
     * @throws ApiException
     */
    public static function throwException(array $error, $args = [], array $logData = [], string $level = 'LogInfo')
    {
        throw new ApiException($error, $args, $logData, $level);
    }

    /**
     * 格式化错误日志
     *
     * @Author YangHB
     * @DateTime 2022-07-13 08:35:57
     *
     * @param Throwable $ex
     * @param int $depth
     * @return array
     */
    public static function normalizeException(Throwable $ex, int $depth = 20): array
    {
        $data = [
            'class' => get_class($ex),
            'message' => $ex->getMessage(),
            'code' => (int)$ex->getCode(),
            'file' => str_replace(root_path(), "", $ex->getFile()),
            'line' => $ex->getLine()
        ];

        $trace = $ex->getTrace();
        if ($depth) {
            $trace = array_slice($trace, 0, $depth);
        }
        foreach ($trace as $frame) {
            if (isset($frame['file'])) {
                $data['trace'][] = sprintf(
                    "%s(%s): %s",
                    str_replace(root_path(), "", $frame['file']),
                    $frame['line'],
                    $frame['function'] ?? "none"
                );
            }
        }

        if ($previous = $ex->getPrevious()) {
            $data['previous'] = self::normalizeException($previous, 1);
        }

        return $data;
    }

    /**
     * 在协程中执行指定业务
     *
     * @param Channel $channel
     * @param int $index
     * @param Closure $callBack
     * @throws ApiException
     */
    public static function runFunctionInCoroutine(Channel $channel, int &$index, Closure $callBack)
    {
        $index++;
        $startTime = getmicrotime();
        $app = app();
        if (!$app instanceof App) {
            throwException(SysErrorCode::SYS_ERROR_OTHER_CODE);
        }
        $trace = Trace::getInstance();
        $request = app('request');
        Coroutine::create(function () use ($channel, $app, $trace, $request, $index, $callBack, $startTime) {
            $app->make(Sandbox::class)->run(function () use ($app, $trace, $channel, $request, $index, $callBack, $startTime) {
                /**
                 * @var App
                 */
                $appOwen = app();
                $appOwen->setInConsole($app->runningInConsole());
                $appOwen->bind(Trace::class, $trace);
                $appOwen->bind(Request::class, $request);
                $result = $callBack();
                $channel->push([
                    'index' => $index,
                    'result' => $result,
                    'useTime' => bcsub(getmicrotime(), $startTime, 6),
                ]);
            });
        });
    }

    public static function callProcessInCoroutine(string $callUrl, string $appName, string $path, array $params)
    {
        /**
         * @var App
         */
        $app = clone app();
        if (!$app instanceof App) {
            throwException(SysErrorCode::SYS_ERROR_OTHER_CODE);
        }
        $trace = Trace::getInstance();
        /**
         * @var Request
         */
        $request = clone app('request');
        $url = $request->url();
        $urlList = explode("?", $callUrl);
        $url = str_replace($request->baseUrl(), $urlList[0], $url);
        $request
            //为了在应用内继续使用原始签名，所以这里暂是不替换基础URL
            //->setBaseUrl($callUrl)
            ->setUrl($url)
            ->setPathinfo($path);
        $requestParam = $request->post();
        foreach ($params as $param) {
            if (is_array($param)) {
                $requestParam = array_merge($requestParam, $param);
            }
        }
        $request->withPost($requestParam);
        $server = $request->server();
        $server['REQUEST_URI'] = $callUrl;
        $server['PATH_INFO'] = $urlList[0];
        $request
            ->withServer($server)
            ->setRoute([]);
        $channel = new Channel(1);
        Coroutine::create(function () use ($channel, $app, $trace, $request, $appName) {
            $app->make(Sandbox::class)->run(function () use ($app, $trace, $channel, $request, $appName) {
                /**
                 * @var App
                 */
                $appOwen = app();
                $appOwen->setInConsole($app->runningInConsole());
                $appOwen->bind(Trace::class, $trace);
                $appOwen->bind(Request::class, $request);
                $result = static::runHttpRequest($request, $appName);
                $channel->push($result);
            });
        });
        return $channel->pop();
    }

    /**
     * 进程内发起HTTP请求处理
     *
     * @Author YangHB
     * @DateTime 2022-08-05 11:11:35
     *
     * @param Request $request
     * @param string $appName
     * @return array|string|mixed
     */
    private static function runHttpRequest($request, $appName)
    {
        /**
         * @var Http
         */
        $http = app(Http::class);
        $http->name($appName);
        $level = ob_get_level();
        ob_start();
        $response = $http->run($request);
        $content = $response->getContent();
        if (ob_get_level() == 0) {
            ob_start();
        }
        $http->end($response);
        if (ob_get_length() > 0) {
            $response->content(ob_get_contents() . $content);
        }
        while (ob_get_level() > $level) {
            ob_end_clean();
        }
        $content = $response->getContent();
        if (stripos($response->getHeader('Content-Type') ?? '', 'application/json') !== false) {
            $contentJson = json_decode($content, true);
            $content = $contentJson ?? $content;
        }
        return $content;
    }
}
